package cn.gs.util.set;

// @author gs
import java.util.*;

public class Cache<K, V> implements Map<K, V> {

    Map<K, CacheObject> caches = new WeakHashMap<K, CacheObject>();
    PriorityQueue<CacheObject> queue = new PriorityQueue();
    private int maxCount;
    private long timeout;

    public Cache(long timeout, int maxCount) {
        this.timeout = timeout <= 0 ? Long.MAX_VALUE : timeout;
        this.maxCount = maxCount <= 0 ? Integer.MAX_VALUE : maxCount;
    }

    public int size() {
        return caches.size();
    }

    public void clear() {
        queue.clear();
        caches.clear();
    }

    public V remove(Object key) {
        CacheObject c = caches.remove(key);
        queue.remove(c);
        return c == null ? null : c.v;
    }

    public V get(Object key) {
        CacheObject c = caches.get(key);
        if (c != null) {
            c.time = System.currentTimeMillis();
            return c.v;
        } else {
            return null;
        }
    }

    public V put(K k, V v) {
        int count;
        while ((count = caches.size()) >= getMaxCount()) {
            while (count >= getMaxCount()) {
                queue.poll();
                --count;
            }
        }
        do {
            CacheObject o = queue.peek();
            if (o != null && o.isTimeout()) {
                queue.poll();
                continue;
            }
        } while (false);
        CacheObject o = caches.put(k, new CacheObject(v));
        return o == null ? null : o.v;
    }

    @Override
    public boolean isEmpty() {
        return caches.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return caches.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        for (CacheObject c : caches.values()) {
            if (c.v == value || (c.v != null && c.v.equals(value))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (Entry<? extends K, ? extends V> e : m.entrySet()) {
            put(e.getKey(), e.getValue());
        }
    }

    @Override
    public Set<K> keySet() {
        return caches.keySet();
    }

    @Override
    public Collection<V> values() {
        List<V> value = new LinkedList<V>();
        for (CacheObject c : caches.values()) {
            value.add(c.v);
        }
        return value;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> value = new HashSet<Entry<K, V>>();
        for (Entry<K, CacheObject> e : caches.entrySet()) {
            value.add(new CacheEntry(e.getKey(), e.getValue()));
        }
        return value;
    }

    public int getMaxCount() {
        return maxCount;
    }

    public void setMaxCount(int maxCount) {
        this.maxCount = maxCount;
    }

    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    private class CacheEntry implements Entry<K, V> {

        K k;
        V v;

        private CacheEntry(K key, CacheObject value) {
            this.k = key;
            this.v = value == null ? null : value.v;
        }

        @Override
        public K getKey() {
            return k;
        }

        @Override
        public V getValue() {
            return v;
        }

        @Override
        public V setValue(V value) {
            v = value;
            return put(k, value);
        }
    }

    private class CacheObject implements Comparable<CacheObject> {

        CacheObject(V v) {
            this.v = v;
        }
        V v;
        long time = System.currentTimeMillis();

        boolean isTimeout() {
            return System.currentTimeMillis() - time > getTimeout();
        }

        @Override
        public int compareTo(CacheObject o) {
            return time - o.time > 0 ? 1 : time == o.time ? 0 : -1;
        }
    }
}
